<script lang="ts" setup>
/**
 * @v-model:date {String | Array<string>>} 必填 当前选中日期
 * 有可选范围时：
 *      选中值 > 默认值 > 今天 > 范围第一天
 *      默认值不在范围时：顺序到 今天
 *      今天不在范围时：顺序到 范围第一天
 * 范围不限时：
 *      选中值 > 默认值 > 今天
 *      默认值不存在时：顺序到 今天
 *
 * Slot:日期="{ day, date }"
 * 插槽名为该日期完整时间 YYYY-MM-DD
 * 可以解构出 day date
 *
 * Emits
 * @dateState 当前组件年月
 *
 * Props
 * @defDateNullable {Boolean} 不传默认值时，不进行择优取值
 * 默认 false
 *
 // * @type {String} 日期选择类型
 // * 默认 single
 // * single   : 单选      : 'YYYY-MM-DD'
 // * multiple : 多选      : ['YYYY-MM-DD', ...]
 // * range    : 范围选    : ['YYYY-MM-DD', 'YYYY-MM-DD']
 *
 * @width {String} 组件宽度
 * 默认 100%
 *
 * @showMoreMouth {Boolean} 显示上下月日期
 *
 * @wrapStyle {Object} 组件容器样式
 *
 * @dateStyle {Object} 组件年月选择区域样式
 *
 * @dateText {String} 年月展示类型 cn | / | -
 * 默认 cn
 * cn : yyyy年mm月
 * /  : yyyy/mm
 * -  : yyyy-mm
 *
 * @weekHeader {String} 组件星期几开头
 * 默认 7
 * 1 ~ 7
 *
 * @weekStyle {Object} 组件星期区域样式
 *
 * @weekText {String} 星期展示类型 cn | number | Array<string>
 * 默认 cn
 * cn     : 日 一 二 ... 六
 * number : 7  1  2 ... 6
 * Array<string> : 自定义显示星期文本
 *
 * @tableThStyle {Object} 组件日期区域 每一行的样式
 *
 * @tableTdStyle {Object} 组件日期区域 每一天的样式
 *
 * @tableTdTextStyle {Object} 组件每一天区域 文字的样式
 *
 * @passStyle {Object} 组件日期区域 不是当前月份的样式
 *
 * @todayStyle {Object} 组件日期区域 今天的样式
 *
 * @disabledStyle {Object} 组件日期区域 禁用的样式
 *
 * @disabledDate {Array} 禁用的日期 ['YYYY-MM-DD', ...]
 *
 * @selectStyle {Object} 组件日期区域 已选择的样式
 *
 * @selectTextStyle {Object} 组件已选择的日期 文字的样式
 *
 * @selectRange {Array} 日期可选范围 ['YYYY-MM-DD', 'YYYY-MM-DD']
 * 范围可单独传入开始或结束 ['', 'YYYY-MM-DD']
 *
 * // 由uniapp自身bug引起的slot-name不生效问题(https://ask.dcloud.net.cn/question/60743)：暂时妥协方案逻辑
 * @dotList {Array} 存在dot点的日期数组 [{ date: 'YYYY-MM-DD', style: {样式} }, ...]
 *
 * @infoList {Array} 存在info的日期数组 [{ date: 'YYYY-MM-DD', info: '信息', style: {样式} }, ...]
 * // ----------------------------------------
 *
 */
import type { PropType } from 'vue'
import { computed, reactive, ref, watch } from 'vue'
import { hasUnit } from '@U/common'
import Icon from '@C/Icon.vue'
import getDate from '@U/getDate'
import type { IObj, IObjArray } from '@/interface'

const props = defineProps({
  width: {
    type: String,
    required: false,
    default: '100%',
  },
  defDateNullable: {
    type: Boolean,
    required: false,
    default: false,
  },
  wrapStyle: {
    type: Object as PropType<IObj>,
    required: false,
    default: () => ({}),
  },
  dateStyle: {
    type: Object as PropType<IObj>,
    required: false,
    default: () => ({}),
  },
  dateText: {
    type: String as PropType<'cn' | '/' | '-'>,
    required: false,
    default: 'cn',
  },
  weekHeader: {
    type: String as PropType<'1' | '2' | '3' | '4' | '5' | '6' | '7'>,
    required: false,
    default: '7',
  },
  weekStyle: {
    type: Object as PropType<IObj>,
    required: false,
    default: () => ({}),
  },
  weekText: {
    type: String as PropType<'cn' | 'number' | Array<string>>,
    required: false,
    default: 'cn',
  },
  tableThStyle: {
    type: Object as PropType<IObj>,
    required: false,
    default: () => ({}),
  },
  tableTdStyle: {
    type: Object as PropType<IObj>,
    required: false,
    default: () => ({}),
  },
  tableTdTextStyle: {
    type: Object as PropType<IObj>,
    required: false,
    default: () => ({}),
  },
  passStyle: {
    type: Object as PropType<IObj>,
    required: false,
    default: () => ({}),
  },
  todayStyle: {
    type: Object as PropType<IObj>,
    required: false,
    default: () => ({}),
  },
  disabledStyle: {
    type: Object as PropType<IObj>,
    required: false,
    default: () => ({}),
  },
  selectStyle: {
    type: Object as PropType<IObj>,
    required: false,
    default: () => ({}),
  },
  selectTextStyle: {
    type: Object as PropType<IObj>,
    required: false,
    default: () => ({}),
  },
  selectRange: {
    type: Array as PropType<Array<string>>,
    required: false,
    default: () => ([]),
  },
  date: {
    type: String,
    required: true,
    default: '',
  },
  // type: {
  //   type: String as PropType<'single' | 'multiple' | 'range'>,
  //   required: false,
  //   default: 'single',
  // },
  showMoreMouth: {
    type: Boolean,
    required: false,
    default: false,
  },
  disabledDate: {
    type: Array as PropType<Array<string>>,
    required: false,
    default: () => ([]),
  },

  // 由uniapp自身bug引起的slot-name不生效问题(https://ask.dcloud.net.cn/question/60743)：暂时妥协方案逻辑
  dotList: {
    type: Array as PropType<IObjArray>,
    required: false,
    default: () => ([]),
  },
  infoList: {
    type: Array as PropType<IObjArray>,
    required: false,
    default: () => ([]),
  },
  // --------------------------------------------------
})
const emits = defineEmits(['update:date', 'dateState'])

const forEnum = [0, 1, 2, 3, 4, 5, 6]
// 样式
const wrapStyles = computed(() => ({ width: hasUnit(props.width), ...props.wrapStyle }))
const passStyles = computed(() => ({
  color: '#bfbfbf',
  ...props.passStyle,
}))
const disabledStyles = computed(() => ({
  color: '#bfbfbf',
  ...props.disabledStyle,
}))
const selectTextStyles = computed(() => ({
  background: 'linear-gradient(328deg, #FF8002 0%, #FCA249 100%)',
  color: 'white',
  width: '52rpx',
  height: '52rpx',
  borderRadius: '50%',
  ...props.selectTextStyle,
}))

// 获取当前时间数据
const current = getDate({ complete: true })
// 默认当前年月
const dateSelectState = reactive(<IObj>{
  Y: current.Y,
  M: +(current.M as string | number),
})
const range_c = computed(() => props.selectRange?.join(''))
watch(
    () => range_c.value,
    () => {
      if (props.selectRange?.length && !!props.selectRange[0]) {
        dateSelectState.Y = +props.selectRange[0].slice(0, 4)
        dateSelectState.M = +props.selectRange[0].split('-')[1]
      }
    },
    { immediate: true },
)

const dateSelectState_C = computed(() => `${dateSelectState.Y}${dateSelectState.M}`)
watch(
    () => dateSelectState_C.value,
    () => emits('dateState', dateSelectState),
)

// 当前表星期头
const weekHeader = props.weekHeader === '7' ? 0 : +props.weekHeader

// 可选日期范围
const range = computed(() => {
  if (!range_c.value) return false
  const start = props.selectRange[0] || false
  const end = props.selectRange[1] || false
  return [start, end]
})
// 该日期是否在可选范围之外
const thisIsRangeDisabled = (date: string) => {
  let rangeStart: boolean | number = false, rangeEnd: boolean | number = false
  if (range.value && range.value[0]) {
    rangeStart = getDate({ date: range.value[0] as string }).time as number
  }
  if (range.value && range.value[1]) {
    rangeEnd = getDate({ date: range.value[1] as string }).time as number
  }
  const dateTime = getDate({ date }).time as number
  let disabled = false
  if (rangeStart && rangeStart > dateTime) {
    disabled = true
  }
  if (rangeEnd && rangeEnd < dateTime) {
    disabled = true
  }
  return disabled
}
// 该日期是否禁用
const thisDateIsDisabled = (date: string, isDisabled: boolean) => {
  const dateList = date.split('-')
  let disabled = false
  props.disabledDate?.forEach((d: string) => {
    const disDateList = d.split('-')
    if (disDateList[0] === dateList[0] && disDateList[1] === dateList[1] && disDateList[2] === dateList[2]) {
      disabled = true
    }
  })

  return disabled || isDisabled
}

// 年月显示文字
const dateText_C = computed(() => {
  const join: string | Array<string> = props.dateText === 'cn' ? ['年', '月'] : props.dateText
  const joinStr = typeof join === 'string'
  const { Y, M } = dateSelectState
  return `${Y}${joinStr ? join : join[0]}${M}${joinStr ? '' : join[1]}`
})
// 星期显示文字
const weekText_C = computed(() => {
  let week = [7, 1, 2, 3, 4, 5, 6]
  let weekCn = ['日', '一', '二', '三', '四', '五', '六']
  const weekOrder = +props.weekHeader
  if (weekOrder !== 7) {
    const weekTemp = week.slice(0, weekOrder)
    const weekCnTemp = weekCn.slice(0, weekOrder)
    week = [...week.slice(weekOrder), ...weekTemp]
    weekCn = [...weekCn.slice(weekOrder), ...weekCnTemp]
  }
  const weekCustom = props.weekText?.length === 7 ? props.weekText : props.weekText === 'cn' ? weekCn : week
  return props.weekText === 'cn'
          ? weekCn
          : props.weekText === 'number'
            ? week
            : weekCustom
})

// 当前选择年月天数
const currentDays = computed(() => {
  if (dateSelectState.Y && dateSelectState.M) {
    // return new Date(dateSelectState.Y, dateSelectState.M, 0).getDate()
    // 兼容ios写法 ↓
    const start = getDate({ date: `${dateSelectState.Y}-${dateSelectState.M}-01`, select: ['time'] }).time as number
    const end = getDate({ date: `${dateSelectState.M === 12 ? dateSelectState.Y + 1 : dateSelectState.Y}-${dateSelectState.M === 12 ? 1 : dateSelectState.M + 1}-01`, select: ['time'] }).time as number
    return (end - start) / 86400000
  }
  return 0
})
// 当前选择年月第一天星期几
const currentDaysOneIsWeek = computed(() => {
  if (dateSelectState.Y && dateSelectState.M) {
    return getDate({ date: `${dateSelectState.Y}-${dateSelectState.M}-01` }).day as number
  }
  return 0
})
// 日期表格多少行
const tableThNumber = computed(() => [...new Array(Math.ceil(
    (
        currentDays.value
        + (currentDaysOneIsWeek.value >= weekHeader ? 0 : forEnum.length)
        + currentDaysOneIsWeek.value
        - weekHeader
    ) / 7,
)).keys()])

// 上一个年月最后一天是几号
const beforeDownDayIsNumber = computed(() => {
  if (dateSelectState.Y && dateSelectState.M) {
    const currentDayTime = getDate({ date: `${dateSelectState.Y}-${dateSelectState.M}-01` }).time
    const beforeDayTime = (currentDayTime as number) - 86400000
    return getDate({ date: beforeDayTime }).D
  }
  return 0
})

// 该日期是否是今天
const thisDateIsToday = (d: IObj) => d.date === `${current.Y}-${current.M}-${current.D}`

// 当前选择年月表格行
const currentDaysTable = computed(() => {
  interface ITd {
    date: string, // 日期
    day: number, // 今天
    disabled: boolean, // 是否禁用
    mouthType: number, // 0 当前月 ； 1 上个月 ； 2 下个月
  }
  const days = currentDays.value
  const th = tableThNumber.value
  const startWeek = currentDaysOneIsWeek.value as number
  const yearAndMonth = `${dateSelectState.Y}-${+dateSelectState.M < 10 ? `0${dateSelectState.M}` : dateSelectState.M}`
  const forLength = forEnum.length

  let td = 1
  const table: Array<Array<ITd>> = []

  th.forEach((i: number) => {
    const thItem: Array<ITd> = []

    if (i === 0) {
      // 第一行
      let beforeWeek = beforeDownDayIsNumber.value as number - startWeek - (weekHeader > startWeek ? forLength - weekHeader : weekHeader ? -1 : 0) + 1

      forEnum.forEach((td_i: number) => {
        if (
            startWeek
                <= td_i + (weekHeader > startWeek
                  ? (forLength - td_i <= weekHeader - startWeek ? forLength : -forLength)
                  : weekHeader)
        ) {
          const date = `${yearAndMonth}-${td < 10 ? `0${td}` : td}`
          thItem.push({ day: td, date, mouthType: 0, disabled: thisIsRangeDisabled(date) })
          td++
        } else {
          let m = `${+dateSelectState.M === 1 ? 12 : (+dateSelectState.M - 1)}`
          if (+m < 10) {
            m = `0${m}`
          }
          const y = +dateSelectState.M === 1 ? (+dateSelectState.Y - 1) : dateSelectState.Y
          const date = `${y}-${m}-${beforeWeek}`
          thItem.push({ day: beforeWeek, date, mouthType: 1, disabled: thisIsRangeDisabled(date) })
          beforeWeek++
        }
      })
    } else if (i === th.length - 1) {
      // 结束行
      let currentMouth = true
      forEnum.forEach(() => {
        if (td - 1 === days) {
          td = 1
          currentMouth = false
        }
        let m = +dateSelectState.M === 12 ? '01' : (+dateSelectState.M + 1)
        if (+m < 10) {
          m = `0${m}`
        }
        const y = +dateSelectState.M === 12 ? (+dateSelectState.Y + 1) : dateSelectState.Y
        const date = `${currentMouth ? yearAndMonth : `${y}-${m}`}-${td < 10 ? `0${td}` : td}`
        thItem.push({ day: td, date, mouthType: currentMouth ? 0 : 2, disabled: thisIsRangeDisabled(date) })
        td++
      })
    } else {
      // 中间行
      forEnum.forEach(() => {
        const date = `${yearAndMonth}-${td < 10 ? `0${td}` : td}`
        thItem.push({ day: td, date, mouthType: 0, disabled: thisIsRangeDisabled(date) })
        td++
      })
    }
    table[i] = thItem
  })
  return table
})

// 年月变化
const changeSelectState = (handle: 1 | -1) => {
  const start = dateSelectState.M === 1
  const end = dateSelectState.M === 12

  if (start && handle === -1) {
    // 去年最后一个月
    dateSelectState.M = 12
    dateSelectState.Y -= 1
  } else if (end && handle === 1) {
    // 明年第一个月
    dateSelectState.M = 1
    dateSelectState.Y += 1
  } else {
    // 本年月份增删
    dateSelectState.M += handle
  }
}

interface IDate {
  day: string;
  date: string;
}
// 当前选择的日期
const selectDate = reactive(<IDate>{
  day: '',
  date: '',
})
const selectDate_c = computed(() => selectDate.date)
// 选择日期
const changeSelectDate = (d: IObj) => {
  const { day, date, mouthType, disabled } = d
  if (thisDateIsDisabled(date, disabled)) return
  if (mouthType === 1) { changeSelectState(-1) }
  if (mouthType === 2) { changeSelectState(1) }
  selectDate.day = day
  selectDate.date = date
  emits('update:date', selectDate.date, selectDate)
}
const date_c = computed(() => props.date)
watch(
    () => date_c.value,
    (newVal: string) => {
      let date = newVal
      const today = `${current.Y}-${current.M}-${current.D}`
      if (!thisIsRangeDisabled(date || today) && !thisDateIsDisabled(date || today, false)) {
        date = date || today
      } else {
        date = (range.value as Array<string>)[0]
      }

      if (!props.defDateNullable || newVal) {
        setTimeout(() => {
          changeSelectDate({
            day: +date.split('-')[2],
            date,
          })
        }, 0)
      }
    },
    { immediate: true },
    // { immediate: props.type === 'single' },
)

// 当前选择的范围日期
const selectRangeDate = reactive({
  start: {
    day: '',
    date: '',
  },
  end: {
    dat: '',
    date: '',
  },
})

// 当前选择的多个日期
const selectMultiDate = ref(<Array<IDate>>[])

// debug: uniapp自身bug引起的slot-name不生效问题(https://ask.dcloud.net.cn/question/60743)
// work:  暂时使用组件内部模板方式完成业务需求
const isDot = (date: string) => {
  if (!props.dotList?.length) return false
  const dotIndex = props.dotList?.findIndex((i: IObj) => i.date === date)
  return props.dotList[dotIndex]
}
const isInfo = (date: string) => {
  if (!props.infoList?.length) return false
  const infoIndex = props.infoList?.findIndex((i: IObj) => i.date === date)
  return props.infoList[infoIndex]
}
// ---------------------------------------------------

/**
 * feat:  组件新增滑动切换
 *
 * // 完全自主实现
 * difficulty:
 *    1. 需要将 当前一组的复杂计算 变为 三组！！！ ，且三组数据全动态关联！！！
 *    2. 需要将组件 整体布局 改为 可滑动
 *    3. 需要计算出一系列滑动操作产生的变量： 斜率，滑动比，滑动时三组布局实时位置，滑动结束时比例是否影响切换 等等
 *    4. 需要根据滑动结束时的滑动比计算出： 自动吸附的距离，新的三组数据与布局 等等
 *
 * // 使用 vant tab 组件套壳，快速实现滑动切换
 * difficulty:
 *    1. 切换之后更新三组数据
 *    2. 如何解决三个Tab滑动之后，数据更新后，还可以继续左右滑动？
 *       - 滑动之后将Tabs key 重置为 1 ？
 *       - Tab组件根据是否 滑动完成/数据更新完成 动态化动画参数 ？
 *
 * const animated = ref(true)
 * const active = ref(1) // 0上个月 | 1当前月 | 2下个月
 */

</script>

<template>
  <div
    :style="wrapStyles"
    class="prm-calendar"
  >
    <!--    <van-tabs-->
    <!--      :active="active"-->
    <!--      :style="wrapStyles"-->
    <!--      :animated="animated"-->
    <!--      swipeable-->
    <!--    >-->
    <!--      <van-tab>0上个月</van-tab>-->
    <!--      <van-tab>1当前月</van-tab>-->
    <!--      <van-tab>2下个月</van-tab>-->
    <!--    </van-tabs>-->
    <div v-if="true" class="prm-calendar-item">
      <div
        :style="{ height: '100rpx', ...dateStyle }"
        class="prm-calendar-date"
      >
        <Icon :click="() => changeSelectState(-1)" :styles="{transform: 'rotate(180deg)'}" name="icon-youjiantou" width="40" />
        <div style="margin: 0 5px">{{ dateText_C }}</div>
        <Icon :click="() => changeSelectState(1)" name="icon-youjiantou" width="40" />
      </div>
      <div
        :style="{ height: '70rpx', ...weekStyle }"
        class="prm-calendar-week"
      >
        <div
          v-for="d in weekText_C"
          :key="d"
          class="prm-calendar-week-item"
        >
          {{ d }}
        </div>
      </div>
      <div class="prm-calendar-table">
        <div
          v-for="(d, i) in tableThNumber"
          :key="d"
          :style="tableThStyle"
          class="prm-calendar-table-th"
        >
          <template
            v-for="dd in currentDaysTable[i]"
            :key="dd.date"
          >
            <div
              v-if="dd.mouthType === 0 || showMoreMouth"
              :style="{
                ...tableTdStyle,
                ...(dd.mouthType !== 0 ? passStyles : {}),
                ...(thisDateIsToday(dd.date) ? todayStyle : {}),
                ...(thisDateIsDisabled(dd.date, dd.disabled) ? disabledStyles : {}),
                ...(selectDate_c === dd.date ? selectStyle : {}),
              }"
              class="prm-calendar-table-td"
              @tap.stop="changeSelectDate(dd)"
            >
              <slot :date="dd.date" :day="dd.day" :name="dd.date">
                <span
                  :style="{
                    ...tableTdTextStyle,
                    ...(selectDate_c === dd.date ? selectTextStyles : {}),
                  }"
                  class="prm-calendar-table-td-text"
                >{{ dd.day }}</span>
              </slot>

              <!-- 由uniapp自身bug引起的slot-name不生效问题(https://ask.dcloud.net.cn/question/60743)：暂时妥协方案逻辑 -->
              <div
                v-if="isDot(dd.date)"
                :style="isDot(dd.date)?.style"
                class="_dot"
              />
              <div
                v-if="isInfo(dd.date)"
                :style="isInfo(dd.date)?.style"
                class="_info"
              >
                {{ isInfo(dd.date).info }}
              </div>
              <!-- ----------------------------------------- -->
            </div>
            <div v-else class="prm-calendar-table-td" />
          </template>
        </div>
      </div>
    </div>
  </div>
</template>

<style lang="less" scoped>
.prm-calendar {
  background: white;
  width: 100%;
  .prm-calendar-item {
    display: flex;
    flex-direction: column;
  }
  .prm-calendar-table {
    flex: 1 1;
  }
}

.prm-calendar-date {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 32rpx;
  color: #262626;
  font-weight: 400;
}
.prm-calendar-week {
  font-size: 28rpx;
  color: #4a4a4a;
  display: flex;
  align-items: center;
  justify-content: space-between;
  .prm-calendar-week-item {
    flex: 1 1;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
  }
}
.prm-calendar-table-th {
  height: 126rpx;
  border-bottom: solid 1rpx #f4f7f7;
  display: flex;
  align-items: center;
  justify-content: space-between;
  &:last-child {
    border-bottom: none;
  }
}
.prm-calendar-table-td-text {
  transition: background-color .3s, color .3s;
  display: flex;
  justify-content: center;
  align-items: center;
}
.prm-calendar-table-td {
  height: 100%;
  width: calc(100% / 7);
  display: flex;
  justify-content: center;
  align-items: center;
  transition: all .3s;
  position: relative;

  // 由uniapp自身bug引起的slot-name不生效问题(https://ask.dcloud.net.cn/question/60743)：暂时妥协方案逻辑
  ._dot {
    position: absolute;
    bottom: 20rpx;
    width: 12rpx;
    height: 12rpx;
    border-radius: 50%;
    background: #ff8002;
  }
  ._info {
    position: absolute;
    bottom: 8rpx;
    width: 100%;
    text-align: center;
    font-size: 24rpx;
    color: #2dc76d;
  }
  // -------------------------------------------------------------
}
</style>
